const Consts    = require('./consts');
const Scheduler = require('./scheduler');

/**
 * 负载均衡器
 * @intro 参考electron-re项目，并做了一些改动
 * @since 1.0.0
 */
class LoadBalancer {
    
    static Algorithm = Consts;
    
    /**
     * @param  {Object} options
     * @param  {Array } options.targets [ targets for load balancing calculation: [{id: 1, weight: 1}, {id: 2, weight: 2}] ]
     * @param  {String} options.algorithm
     */
    constructor(options) {
        this.targets    = options.targets;
        this.algorithm  = options.algorithm || Consts.polling;
        this.params     = { // data for algorithm
            currentIndex      : 0, // index
            weightIndex       : 0, // index for weight alogrithm
            weightTotal       : 0, // total weight
            connectionsMap    : {}, // connections of each target
            cpuOccupancyMap   : {}, // cpu occupancy of each target
            memoryOccupancyMap: {} // cpu occupancy of each target
        };
        this.scheduler  = new Scheduler(this.algorithm);
        this.memoParams = this.memorizedParams();
        this.calculateWeightIndex();
    }
    
    /**
     * 算法参数
     */
    memorizedParams() {
        return {
            [Consts.random]                  : () => [],
            [Consts.polling]                 : () => [this.params.currentIndex, this.params],
            [Consts.weights]                 : () => [this.params.weightTotal, this.params],
            [Consts.specify]                 : (id) => [id],
            [Consts.weightsRandom]           : () => [this.params.weightTotal],
            [Consts.weightsPolling]          : () => [this.params.weightIndex, this.params.weightTotal, this.params],
            [Consts.minimumConnection]       : () => [this.params.connectionsMap],
            [Consts.weightsMinimumConnection]: () => [this.params.weightTotal, this.params.connectionsMap, this.params]
        };
    }
    
    /**
     * 刷新参数
     */
    refreshParams(pidMap) {
        const infos = Object.values(pidMap);
        for (let info of infos) {
            // this.params.connectionsMap[id] = connections;
            this.params.cpuOccupancyMap[info.pid]    = info.cpu;
            this.params.memoryOccupancyMap[info.pid] = info.memory;
        }
    }
    
    /**
     * 选举出一个进程
     */
    pickOne(...params) {
        return this.scheduler.calculate(
            this.targets, this.memoParams[this.algorithm](...params)
        );
    }
    
    /**
     * 选举出多个进程
     */
    pickMulti(count = 1, ...params) {
        return new Array(count).fill().map(
            () => this.pickOne(...params)
        );
    }
    
    /**
     * 计算权重
     */
    calculateWeightIndex() {
        this.params.weightTotal = this.targets.reduce((total, cur) => total + (cur.weight || 0), 0);
        if (this.params.weightIndex > this.params.weightTotal) {
            this.params.weightIndex = this.params.weightTotal;
        }
    }
    
    /**
     * 计算索引
     */
    calculateIndex() {
        if (this.params.currentIndex >= this.targets.length) {
            this.params.currentIndex = (this.params.currentIndex - 1 >= 0) ? (this.params.currentIndex - 1) : 0;
        }
    }
    
    /**
     * 清除data
     */
    clean(id) {
        if (id) {
            delete this.params.connectionsMap[id];
            delete this.params.cpuOccupancyMap[id];
            delete this.params.memoryOccupancyMap[id];
        } else {
            this.params = {
                currentIndex      : 0,
                connectionsMap    : {},
                cpuOccupancyMap   : {},
                memoryOccupancyMap: {}
            };
        }
    }
    
    /**
     * 添加一个进程信息
     */
    add(task) {
        if (this.targets.find(target => target.id === task.id)) {
            return console.warn(`Add Operation: the task ${task.id} already exists.`);
        }
        this.targets.push(task);
        this.calculateWeightIndex();
    }
    
    /**
     * 删除一个进程信息
     */
    del(target) {
        let found = false;
        for (let i = 0; i < this.targets.length; i++) {
            if (this.targets[i].id === target.id) {
                this.targets.splice(i, 1);
                this.clean(target.id);
                this.calculateIndex();
                found = true;
                break;
            }
        }
        
        if (found) {
            this.calculateWeightIndex();
        } else {
            console.warn(`Del Operation: the task ${target.id} is not found.`, this.targets);
        }
    }
    
    /**
     * 擦除
     */
    wipe() {
        this.targets = [];
        this.calculateWeightIndex();
        this.clean();
    }
    
    /**
     * 更新计算参数
     */
    updateParams(object) {
        Object.entries(object).map(([key, value]) => {
            if (key in this.params) {
                this.params[key] = value;
            }
        });
    }
    
    /**
     * 设置targets
     */
    setTargets(targets) {
        const targetsMap = targets.reduce((total, cur) => {
            total[cur.id] = 1;
            return total;
        }, {});
        this.targets.forEach(target => {
            if (!(target.id in targetsMap)) {
                this.clean(target.id);
                this.calculateIndex();
            }
        });
        this.targets = targets;
        this.calculateWeightIndex();
    }
    
    /**
     * 设置算法
     */
    setAlgorithm = (algorithm) => {
        if (algorithm in Consts) {
            this.algorithm          = algorithm;
            this.params.weightIndex = 0;
            this.scheduler.setAlgorithm(this.algorithm);
        } else {
            throw new Error(`Invalid algorithm: ${algorithm}, pick from ${Object.keys(Consts).join('|')}`);
        }
    }
}

module.exports = LoadBalancer;